using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;

namespace SteeringBehaviors
{
    public partial class TaskForce
    {
        #region PRIVATE MEMBERS

        bool wrapOutOfBounds;
        bool leader;
        bool stickToMinSpeed;
        float minSpeed;
        float cohesionStrength;
        float separationStrength;
        float alignmentStrength;
        Vector3 focusForward;
        Vector3 focusPoint;
        ColorValue color;
        List<Ship> ships;
        protected SortedList<string, int> amountOfType;

        #endregion

        #region PROPERTIES

        /// <summary>
        /// The color of all ships in this TaskForce
        /// </summary>
        public ColorValue Color
        {
            get { return color; }
            set
            {
                color = value;
                for (int i = 0; i < ships.Count; ++i)
                    ships[i].DXColor = color;
            }
        }

        /// <summary>
        /// True - ships wrap to other side of bound game area when out of bounds
        /// False - ships stay within their side of the bound game area
        /// </summary>
        public bool WrapOutOfBounds
        {
            get { return wrapOutOfBounds; }
            set { wrapOutOfBounds = value; }
        }

        /// <summary>
        /// True - cohesion and alignment algorithms utilize leader information
        /// False - cohesion and alignment algorithms utilize average ship infomation
        /// </summary>
        public bool StickToLeader
        {
            get { return leader; }
            set { leader = value; }
        }

        /// <summary>
        /// True - Each ship in the task force moves at the max speed of the slowest ship in the task force
        /// False - Each ship in the task force moves at their own max speed
        /// </summary>
        public bool StickToMinSpeed
        {
            get { return stickToMinSpeed; }
            set { stickToMinSpeed = value; }
        }

        /// <summary>
        /// Value ranging from zero to one that is used to determine strength or impact of cohesion algorithm.
        /// Set to 0 to disable the cohesion algorithm.
        /// <remarks>Value is clamped to zero if less or one if greater then the allowed range.</remarks>
        /// </summary>
        public float CohesionStrength
        {
            get {return cohesionStrength;}
            set
            {
                cohesionStrength = value;
                if (cohesionStrength > 1f)
                    cohesionStrength = 1f;
                else if (cohesionStrength < 0f)
                    cohesionStrength = 0f;
            }
        }

        /// <summary>
        /// Value ranging from zero to one that is used to determine strength or impact of separation algorithm.
        /// Set to zero to disable the separation algorithm.
        /// <remarks>Value is clamped to zero if less or one if greater then the allowed range.</remarks>
        /// </summary>
        public float SeparationStrength
        {
            get { return separationStrength; }
            set
            {
                separationStrength = value;
                if (separationStrength > 1f)
                    separationStrength = 1f;
                else if (separationStrength < 0f)
                    separationStrength = 0f;
            }
        }

        /// <summary>
        /// Value ranging from zero to one that is used to determine strength or impact of alignment algorithm.
        /// Set to zero to disable the alignment algorithm.
        /// <remarks>Value is clamped to zero if less or one if greater then the allowed range.</remarks>
        /// </summary>
        public float AlignmentStrength
        {
            get { return alignmentStrength; }
            set
            {
                alignmentStrength = value;
                if (alignmentStrength > 1f)
                    alignmentStrength = 1f;
                else if (alignmentStrength < 0f)
                    alignmentStrength = 0f;
            }
        }

        /// <summary>
        /// Gets/Sets the list of ships currently assigned to this task force.
        /// </summary>
        public List<Ship> Ships
        {
            get { return ships; }
            set { ships = value; }
        }

        #endregion

        #region CONSTRUCTORS

        /// <summary>
        /// Default constructor
        /// </summary>
        public TaskForce()
        {
            WrapOutOfBounds = true;
            color = new ColorValue(1f, 1f, 1f, 1f);
            StickToLeader = false;
            ships = new List<Ship>();
            focusPoint = new Vector3();
            focusForward = new Vector3(1, 0, 0);
            AlignmentStrength = SeparationStrength = CohesionStrength = 1;
            minSpeed = 1000f;
            StickToMinSpeed = true;
            amountOfType = new SortedList<string, int>();
        }

        #endregion

        #region INTERFACE METHODS

        /// <summary>
        /// Gets a string representation of this task force.
        /// </summary>
        /// <returns>a string representation of this task force</returns>
        public override string ToString()
        {
            //return base.ToString();
            return "Task Force of " + ships.Count + " ships. Color {A:" + color.Alpha.ToString()+ " R:"+ color.Red.ToString() +
                " G:" + color.Green.ToString() + " B:" + color.Blue.ToString() + "}";
        }

        /// <summary>
        /// Gets the number of ships of a specified type contained in this task force.
        /// </summary>
        /// <param name="shipType">The name of the specified type</param>
        /// <returns>the number of ships of a specified type contained in this task force</returns>
        public int GetNumberOfShipType(string shipType)
        {
            if(amountOfType.ContainsKey(shipType))
                return amountOfType[shipType];
            return 0;
        }

        /// <summary>
        /// Assigns a ship to this task force.
        /// </summary>
        /// <param name="addMe">ship to assign to task force</param>
        public virtual void AddShip(Ship addMe)
        {
            addMe.DXColor = color;
            ships.Add(addMe);
            ResetMinSpeed();
            if (amountOfType.ContainsKey(addMe.Type))
                amountOfType[addMe.Type]++;
            else
                amountOfType.Add(addMe.Type, 1);
        }

        /// <summary>
        /// Removes all ships from this task force.
        /// </summary>
        /// <returns>the ships previously assigned to this task force</returns>
        /// <remarks>This is an expensive operation.</remarks>
        public virtual List<Ship> Disband()
        {
            List<Ship> shipsToDisband = new List<Ship>(ships);
            ships.Clear();
            amountOfType.Clear();
            return shipsToDisband;
        }

        /// <summary>
        /// Attempts to remove the ship with the given id value
        /// </summary>
        /// <param name="id">given id value</param>
        /// <returns>the removed ship</returns>
        /// <remarks>this method will return null if no ship was removed</remarks>
        public virtual Ship DisbandShip(uint id)
        {
            Ship removeMe = null;
            for (int i = 0; i < ships.Count; ++i)
            {
                if (ships[i].ID == id)
                {
                    removeMe = ships[i];
                    ships.RemoveAt(i);
                    break;
                }
            }
            ResetMinSpeed();
            amountOfType[removeMe.Type]--;
            return removeMe;
        }

        /// <summary>
        /// Restricts ship movement to the bounding size provided
        /// </summary>
        /// <param name="bounds">size of the bounding area</param>
        public void BoundsCheck(Vector3 bounds)
        {
            for (int i = 0; i < ships.Count; ++i)
            {
                Vector3 newPosition = new Vector3();
                newPosition = ships[i].Position;
                if (WrapOutOfBounds)
                {
                    if (newPosition.X > bounds.X + ships[i].Dimensions.Width)
                        newPosition.X = -ships[i].Dimensions.Width;
                    if (newPosition.X < -ships[i].Dimensions.Width)
                        newPosition.X = bounds.X + ships[i].Dimensions.Width;
                    if (newPosition.Y > bounds.Y + ships[i].Dimensions.Height)
                        newPosition.Y = -ships[i].Dimensions.Height;
                    if (newPosition.Y < -ships[i].Dimensions.Height)
                        newPosition.Y = bounds.Y + ships[i].Dimensions.Height;
                }
                else
                {
                    if (newPosition.X > bounds.X)
                        newPosition.X = bounds.X;
                    if (newPosition.X < 0)
                        newPosition.X = 0;
                    if (newPosition.Y > bounds.Y)
                        newPosition.Y = bounds.Y;
                    if (newPosition.Y < 0)
                        newPosition.Y = 0;
                }
                ships[i].Position = newPosition;
            }
        }

        /// <summary>
        /// Renders all ships currently assigned to this task force
        /// </summary>
        /// <param name="sprite">DirectX sprite object to use for rendering</param>
        public void Render(Microsoft.DirectX.Direct3D.Sprite sprite)
        {
            for (int i = 0; i < Ships.Count; ++i)
                Ships[i].Render(sprite);
        }

        #endregion

        #region HELPER METHODS

        /// <summary>
        /// Resets the taskforce speeds of all ships within this taskforce
        /// </summary>
        private void ResetMinSpeed()
        {
            if (ships.Count > 0)
            {
                if (StickToMinSpeed)
                {
                    minSpeed = ships[0].MaxSpeed;
                    for (int i = 1; i < ships.Count; ++i)
                        if (ships[i].MaxSpeed < minSpeed)
                            minSpeed = ships[i].MaxSpeed;
                    for (int i = 0; i < ships.Count; ++i)
                        ships[i].TaskForceSpeed = minSpeed;
                }
                else
                {
                    for (int i = 0; i < ships.Count; ++i)
                        ships[i].TaskForceSpeed = ships[i].MaxSpeed;
                }
            }
        }

        /// <summary>
        /// Determines the closest ship to the provided ship's position and the distance between the two
        /// </summary>
        /// <param name="fromThisShip">the provided ship</param>
        /// <param name="closestShipIndex">OUT PARAM: the index (NOT ID) of the closest ship</param>
        /// <returns>the distance from the provided ship to the closest ship</returns>
        /// <remarks>this will return zero and set otherShipIndex to zero if only one ship is present in this TaskForce</remarks>
        private float GetNearestTaskForceShipDistance(Ship fromThisShip, out int closestShipIndex)
        {
            if (ships.Count < 2)
            {
                closestShipIndex = 0;
                return 0;
            }
            Vector3 distanceVector = new Vector3();
            float distanceSquared, temp;
            if (ships[0].ID == fromThisShip.ID)
            {
                distanceVector = ships[1].Position - fromThisShip.Position;
                distanceSquared = distanceVector.LengthSq();
                closestShipIndex = 1;
            }
            else
            {
                distanceVector = ships[0].Position - fromThisShip.Position;
                distanceSquared = distanceVector.LengthSq();
                closestShipIndex = 0;
            }
            for (int i = 0; i < ships.Count; ++i)
            {
                if (ships[i].ID == fromThisShip.ID)
                    continue;
                distanceVector = ships[i].Position - fromThisShip.Position;
                temp = distanceVector.LengthSq();
                if (temp < distanceSquared)
                {
                    distanceSquared = temp;
                    closestShipIndex = i;
                }
            }
            return Convert.ToSingle(Math.Sqrt(distanceSquared));
        }

        /// <summary>
        /// Determines if the provided ship will collide or is colliding with the closest ship
        /// </summary>
        /// <param name="ship">the provided ship</param>
        /// <param name="closestShipIndex">OUT PARAM: the index (NOT ID) of the closest ship</param>
        /// <returns>true if the provided ship will collide or is colliding with the closest ship</returns>
        /// <remarks>this will return false and set otherShipIndex to zero if only one ship is present in this TaskForce</remarks>
        private bool PredictCollision(Ship ship, out int otherShipIndex)
        {
            if (ships.Count < 2)
            {
                otherShipIndex = 0;
                return false;
            }
            float distance = GetNearestTaskForceShipDistance(ship, out otherShipIndex);
            return (distance <= ships[otherShipIndex].Radius * 3f);
        }

        #endregion
    }
}